Skip to content

fix(devx): seven gates that import typescript refuse an uninstalled tree with a named prerequisite, not a raw stack (#15783) - #15862

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-15783-typescript-import-thunks
Sep 5, 2026
Merged

fix(devx): seven gates that import typescript refuse an uninstalled tree with a named prerequisite, not a raw stack (#15783)#15862
baozhoutao merged 3 commits into
mainfrom
claude/issue-15783-typescript-import-thunks

Conversation

@claude

@claude claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #15783

Seven gate/census scripts still carried the bare dependency import that PR #11824 converted everywhere else, so on a fresh per-task worktree — the checkout shape CLAUDE.md mandates, with pnpm install never run — they died with a node-internals ERR_MODULE_NOT_FOUND stack and exit 1, the code a real finding uses. Each now loads its dependency through scripts/import-prerequisite.mjs and answers PREREQUISITE NOT MET, exit 3, naming the missing package and saying nothing was measured.

Population, derived on this base (not recalled)

The card's own re-check, with its control, at merge base 9e9f03abe:

$ git grep -n "^import ts from 'typescript'" -- scripts
scripts/audits/14744-before-update-per-row-value-census.mjs:71
scripts/check-exported-any-returns.mts:121
scripts/check-sdui-lockstep.mjs:112
scripts/isystem-census.mjs:78            <- the card's subject
scripts/measure-durability-swallow-family.mjs:259
scripts/tenant-audit-census.mjs:147
                                          => 6, matching triage's count

$ git grep -ln "requireDefaultExport" -- scripts | wc -l
27                                        (the converted set; the control is live)

Plus the seventh site recorded in this card's 09:33Z comment, which that grep cannot see because it is a dynamic import of a different package — scripts/check-comment-mask-corpus.mjs:306, @typescript-eslint/parser. Deferring resolution past linking changes when the failure happens, not what it looks like: the rejection reaches the top level unhandled and node prints the same stack with the same exit 1. It is a CI gate (lint.yml) and a dispatch-gates --changed --commands member, so a dev harvesting the derived family got exit 3 with a named prerequisite from two commands and a raw stack from a third, for one missing install.

Deviation from the Claim ruling, with the evidence for it

The ruling excluded scripts/measure-durability-swallow-family.mjs:259 as #15766's live hot file and asked for it as a follow-up. The re-dispatch records that #15808 landed and the file is free; measured rather than assumed — no open PR touches any file in this diff:

GET /repos/objectstack-ai/objectstack/pulls?state=open   -> 27 PRs
GET /repos/.../pulls/N/files for all 27, intersected with this file list
  -> the only hit is PR #15395 on scripts/check-system-context-census.mjs,
     which this PR does not touch (the fix is one level down, in isystem-census.mjs)

So it is converted here rather than re-pointed. scripts/check-system-context-census.mjs is deliberately left alone.

The eighth file: why a .d.mts sidecar

check-exported-any-returns.mts runs through the same loader (node 22 type-stripping; tsx via @objectstack/client's script), so the ruling's conditional is satisfied and it is converted. Two things it needs that the six .mjs sites do not:

  • Type positions. The value binding stops being an import, so ts.Program/ts.Type/ts.Signature in annotations move to import type TS from 'typescript', erased before the graph is linked. Every ts. left in the file is a value use and reads the thunked binding.
  • scripts/import-prerequisite.d.mts. That file is inside the ROOT tsc program, where an untyped .mjs import is TS7016. Measured, not assumed: with the conversion and no sidecar the root program reported 169 errors against 168 for the identical tree carrying main's copy of the file — and @objectstack/spec-monorepo is a shrink-only ratchet. The sidecar is the shape check-regen-pending.d.mts, invoked-as.d.mts and js-comment-mask.d.mts already use for exactly this reason, and check:declaration-mirrors covers it by discovery (name / kind / required arity — Function.length is 3 on both loaders, matching the declared 3).

Measurements

BEFORE, dep-less worktree (mkdir node_modules, no install), exit captured before any pipe — all seven sites:

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'typescript' imported from
  /home/user/objectstack-15783/scripts/isystem-census.mjs
EXIT=1

identically for check-sdui-lockstep.mjs, tenant-audit-census.mjs, audits/14744-…census.mjs, measure-durability-swallow-family.mjs, check-exported-any-returns.mts, and — for @typescript-eslint/parsercheck-comment-mask-corpus.mjs (its --self-test leg too, measured against origin/main's copy of the file in the same tree).

AFTER, dep-less, at this PR's head ddcf7c133 (a second --detach worktree with mkdir node_modules and no install), ten invocations, all exit 3:

$ pnpm -s check:system-context-census ; echo EXIT=$?     # the card's own spelling
ts-parse: PREREQUISITE NOT MET — the dependency `typescript` is not installed
EXIT=3

$ node scripts/check-exported-any-returns.mts ; echo EXIT=$?
check-exported-any-returns.mts: PREREQUISITE NOT MET — the dependency `typescript` is not installed
  … result says NOTHING about whether any exported callable of an SDK package resolves to `any`.
EXIT=3

$ node scripts/check-comment-mask-corpus.mjs ; echo EXIT=$?
check-comment-mask-corpus: PREREQUISITE NOT MET — the dependency `@typescript-eslint/parser` is not installed
EXIT=3

plus check-sdui-lockstep (both legs), tenant-audit-census, measure-durability-swallow-family --self-test=gated, the 14744 audit, and check-comment-mask-corpus --self-test.

Which module names itself is worth reading rather than assuming: the five sites that also import { parseSourceFile } from './ts-parse.mjs' refuse as ts-parse, because a static import is evaluated before the importing module's body — the same shape the card's positive control shows. That is why no measures string is passed at those five: it would be text that can never print. The two sites whose own thunk fires first (check-exported-any-returns.mts, check-comment-mask-corpus.mjs) do pass one, and it appears in the output above.

Installed tree, each site still runs its real check:

invocation exit reading
check:system-context-census (self-test + gate) 0 green
check:sdui-lockstep (self-test + gate) 0 green
check:swallow-census-controls 0 green
check-comment-mask-corpus.mjs 0 5998 files, 0 disagree, 0 unparseable, 55.1s
check-comment-mask-corpus.mjs --self-test 0 All 17 self-test cases passed.
node scripts/tenant-audit-census.mjs 1 its genuine census findings; its gate check-tenant-audit-census.mjs is 0
audits/14744-…census.mjs 0 prints its census
@objectstack/client check:exported-any-returns (via tsx, closure + package built) 0 317 callables reached … 36 ledgered site(s) still open

Ablation (scripts/isystem-census.mjs, trap-guarded, on the committed implementation): thunk reverted to the bare import — proven on disk, thunk=0 bare=1 — the raw ERR_MODULE_NOT_FOUND stack and exit 1 come back; restored with git checkout HEAD -- PATH, git hash-object equal to the HEAD blob 69cb3d34c2c0744f00791ec13e26088cf0a42f7e, git diff HEAD empty, and the gate answers exit 3 again.

Gate family, node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack, re-derived on this head (no staleness warning): 35 commands, all run, reconciliation 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. 34 exit 0. Also pnpm check:nul-bytes 0, a manual grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over the eight files with no match, check-governed-merges --test FINAL-FILE-LIST0 of 8 path(s) hit the register, and git merge-tree --write-tree origin/main HEAD after a fresh fetch → exit 0, no conflict (no path in this diff carries a merge driver).

NOT MEASURED, by name: pnpm check:type-check-debt — exit 3, --re-measure cannot run: 1 workspace dependenc(ies) … have no built type entry point on disk. Its coverage half ran and printed OK; the ratchet entry this diff could move is the root program, and that was measured directly instead: identical tree, npx tsc -p tsconfig.json --noEmit reports 31 errors with this PR's .mts + sidecar and 31 with main's copy of the file and no sidecar, 0 of them in the converted file. CI runs the full-closure re-measure.

Follow-ups (not in this PR)

  • prerequisiteNotMetText strips only .mjs when it derives the gate's name, so the first .mts consumer labels itself check-exported-any-returns.mts. Cosmetic, and only observable from this PR onward.
  • @objectstack/spec-monorepo's ledger note is declared stale by its own compositionAt (tallied at 80, recorded 26). Pre-existing, surfaced by the gate itself.

🤖 Generated with Claude Code

https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk


Generated by Claude Code

… tree with a named prerequisite, not a raw stack

`check:system-context-census` and six sibling sites still carried the bare
top-level `import ts from 'typescript'` (and, at one site, a bare dynamic
`import('@typescript-eslint/parser')`) that PR #11824 converted everywhere else.
node resolves those before any module body runs, so the gate cannot preflight
its own missing dependency: on a fresh worktree — the checkout shape CLAUDE.md
mandates — it died with a node-internals `ERR_MODULE_NOT_FOUND` stack and exit
**1**, the same code a real finding uses.

Each site now loads the dependency through `scripts/import-prerequisite.mjs`,
in the shape the 27 already-converted sites use, so the answer is
`PREREQUISITE NOT MET`, exit 3, and an explicit "nothing was measured".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
…an import them without TS7016

`check-exported-any-returns.mts` is inside the ROOT tsc program, where an
untyped `.mjs` import is TS7016 — measured, not assumed: converting it added
exactly one error (169 vs the 168 the same tree reports with the unconverted
file), and `@objectstack/spec-monorepo` is a shrink-only ratchet. The sidecar is
the shape three root scripts already use for the same reason, and
`check:declaration-mirrors` covers it by discovery.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Flipped ready + auto-merge enabled (11:52Z, method: MERGE). All seven required contexts on ddcf7c133 read non-failing: Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Governed Surface Queue Guard success; Build Core, Temporal Conformance (live PG + MySQL) skipped; no other red. git merge-tree --write-tree origin/main <branch> → EXIT=0 against 7b6825477. Watched; on landing #15783 closes.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants